#include <eosio/eosio.hpp>
#include <eosio/time.hpp>
#include <eosio/system.hpp>
#include <eosio/singleton.hpp>
#include <eosio/asset.hpp>
#include <eosio/multi_index.hpp>
#include <eosio/action.hpp>
#include <eosio/crypto.hpp>
#include <eosio/transaction.hpp>


/**
 * 合同 数据库结构及增删改查
 * jungle4 合同地址 zdemlmtc4ug4
 */
CONTRACT db_contract : public eosio::contract {
	public:
	using contract::contract;

	// 表 结构 定义 数据结构
	TABLE repo {
		uint64_t id;
		eosio::name eos_account;
		uint64_t num;
		eosio::time_point_sec create_time;
		eosio::time_point_sec update_time;
		std::string text;
		eosio::checksum256 transaction_id; // transaction_id 为capi_checksum256类型数据

		uint64_t primary_key() const {
			return id;
		}
		uint64_t account_index() const {
			return eos_account.value;
		}
		uint64_t num_index() const {
			return num;
		}
        //二级索引为sha256类型
        eosio::checksum256 hash_index() const {
            return checksum256_to_sha256(transaction_id);
        }
	};
	// 单例表结构
	TABLE info {
		uint64_t call_num;
		eosio::time_point_sec last_call_time;
	};

	// 数据库 表构造函数
	using repo_table = eosio::multi_index<
	"repos"_n, repo, // 数据库名称
	eosio::indexed_by<"byaccount"_n, eosio::const_mem_fun<repo, uint64_t, &repo::account_index>>,
	eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<repo, eosio::checksum256, &repo::hash_index>>,
	eosio::indexed_by<"bynum"_n, eosio::const_mem_fun<repo, uint64_t, &repo::num_index>>
	>;
	using info_table = eosio::singleton<"info"_n, info>;

	// 外部调用方法
	ACTION save(eosio::name eos_account, std::string text){
		// 自己 修改 自己的数据
		require_auth(eos_account);
		eosio::name ram_payer = get_self();

		// 实例化数据库
		repo_table repos(get_self(), get_self().value);

		// 找到数据
		auto index_account = repos.get_index<"byaccount"_n>();
		uint64_t max_num = 1;
		// index_account.iterator_to(*find_repos); // 查找位置
		// find 索引顺序 往下找
		for (auto find_repos = index_account.find(eos_account.value);find_repos != index_account.end();find_repos++) { // 存在 取出 id
			eosio::print("find id:",find_repos->id,"\n");
			if (eos_account != find_repos->eos_account) {
				break;
			}
			max_num = find_repos->num + 1;
		}
        //获取transaction id
        eosio::checksum256 transaction_id = get_hash();
		eosio::print("将要插入 合约账户:", get_self(),",repo表:", eos_account.value, "最后一条消息数", std::to_string(max_num));
		repos.emplace(ram_payer, [&](auto& row) {
			row = repo {
				.id = repos.available_primary_key(), // 自增
				.eos_account = eos_account,
				.num = max_num,
				.text = text,
				//.transaction_id = transaction_id,
				.create_time = eosio::current_time_point()
			};
		});
		on_call();
	}

	ACTION remove(eosio::name eos_account, uint64_t num){
		// 自己 修改 自己的数据
		require_auth(eos_account);
		// 实例化数据库
		repo_table repos(get_self(), get_self().value);

		// 找到数据
		auto index_account = repos.get_index<"byaccount"_n>();
		eosio::check(index_account.end()!=index_account.begin(), "数据库是空");
		auto find_repos = index_account.upper_bound(eos_account.value);
		eosio::check(find_repos!=index_account.begin(), "数据未找到");
		// upper_bound 是大于 先执 往下走
		while(true) {
			find_repos--;
			eosio::print("find id:",find_repos->id,"\n");
			eosio::check(find_repos->eos_account == eos_account, "用户数据未找到");
			eosio::check(find_repos->num >= num, "编号数据未找到");
			if (find_repos->num == num) {
				repos.erase(*find_repos);
				on_call();
				return;
			}
			if (find_repos == index_account.begin()) {
				break;
			}
		}
		eosio::check(false, "未找到数据");
	}

	ACTION modify(eosio::name eos_account, uint64_t num, std::string text){
		// 自己 修改 自己的数据
		// require_auth(eos_account);
		eosio::internal_use_do_not_use::require_auth2(eos_account.value, eosio::name("active").value);
		// 实例化数据库
		repo_table repos(get_self(), get_self().value);

		// 找到数据
		auto index_account = repos.get_index<"byaccount"_n>();
		eosio::check(index_account.end()!=index_account.begin(), "数据库是空");
		auto find_repos = index_account.upper_bound(eos_account.value);
		eosio::check(find_repos!=index_account.begin(), "数据未找到");
		eosio::name ram_payer = get_self();
		// upper_bound 是大于 先执 往下走
		while(true) {
			find_repos--;
			eosio::print("find id:",find_repos->id,"\n");
			eosio::check(find_repos->eos_account == eos_account, "用户数据未找到");
			eosio::check(find_repos->num >= num, "编号数据未找到");
			if (find_repos->num == num) {
				repos.modify(*find_repos, ram_payer, [&](auto& row) {
					row.text = text;
					row.update_time = eosio::current_time_point();
				});
				on_call();
				return;
			}
			if (find_repos == index_account.begin()) {
				break;
			}
		}
		eosio::check(false, "未找到数据");
	}

	[[eosio::action]] repo find(uint64_t id){
        repo_table repos(get_self(), get_self().value);
        return repos.get(id, "repo not found");
    }

	[[eosio::action]] std::string notify(eosio::name eos_account, uint64_t id, bool is_error){
        eosio::require_recipient(eos_account);// 自已的 notify 调用不了自己的 on_notify
        std::string ret = "{error:" + std::to_string(is_error) + ",id:" + std::to_string(id) + ",name:" + std::to_string(eos_account.value) + "}";
        eosio::print("notify:", ret);
        return ret;
    }

    [[eosio::on_notify ("*::notify")]]
    bool on_notify(eosio::name eos_account, uint64_t id, bool is_error){
        // 自定义回调使用 合约名称[*] action[notify] 并且 使用了 require_recipient(void 返回) 通知
        on_call();
        if (is_error) {
            // notify 出错 事务 会 回滚
            eosio::check(false, "强制回滚");
            eosio::print("on_notify: 强制回滚", get_self(), " find ", id);
            return is_error;
        }
        eosio::print("on_notify:", get_self(), " find ", id);
        return is_error;
    }

	private:
	void on_call(){
		info_table info_configs(get_self(), get_self().value);
		eosio::name ram_payer = get_self();
		info config = info_configs.get_or_create(ram_payer,info{
			.call_num = 0,
			.last_call_time = eosio::current_time_point()
		});
		config.call_num++;
		config.last_call_time = eosio::current_time_point();
		info_configs.set(config, ram_payer);
	}
	static eosio::checksum256 checksum256_to_sha256(const eosio::checksum256 &hash) {
        const uint128_t *p128 = reinterpret_cast<const uint128_t *>(&hash);
        eosio::checksum256 k;
        k.data()[0] = p128[0];
        k.data()[1] = p128[1];
        return k;
    }
    eosio::checksum256 get_hash(){
        eosio::checksum256 transaction_id;
        auto size = eosio::transaction_size();
        char* buf = new char[size];
        uint32_t read = eosio::read_transaction(buf, size);
        transaction_id = eosio::sha256(buf, read);
		eosio::printhex( &buf, sizeof(buf) );
		eosio::print("\n checksum256 get_hash 1 transaction_id:", transaction_id, " buf ", buf, "read", read, "\n");
        delete[] buf;
        transaction_id = eosio::sha256(buf, read);
		eosio::printhex( &buf, sizeof(buf) );
		eosio::print("\n checksum256 get_hash 2 transaction_id:", transaction_id, " buf ", buf, "read", read, "\n");
        return transaction_id;
    }
};